September 03, 2023
값 객체가 가지는 값의 성질
값 객체의 장점
값 객체는 도메인 지식을 코드화 한 것 이다.
값 객체는 데이터와 함께 데이터에 행해질 수 있는 액션들을 함께 가지고 있다.
값 객체는 말 그대로 값 + 객체를 의미한다. 따라서 값 객체는 값의 성질과 객체의 성질을 모두 만족해야 한다.
값 객체가 가지는 값이 가지는 속성은 어떤 것들이 있을까?
값 객체는 불변성을 가지고 있다.
불변성이라는 것은 값 객체 내부 속성들이 변경 불가능하다는 의미이다. 불변성을 가진 값 객체는 속성을 바꾸고 싶더라도 변경할 수 없어야 한다.
만약 값 객체가 변경 가능하다면 데이터가 어떻게 변경될지 알 수 없기 때문에 예상치 못한 사이드 이펙트 등의 버그가 발생할 수 있다. 값 객체의 불변성은 버그를 줄이고 신뢰성을 높인다.
그렇다면 값 객체를 변경하려면 어떻게 해야하는가? 새로운 값 객체를 생성하여 교체하면 된다.
불변 객체가 변경될 수 있는 방법은 새로운 값 변수를 만들어 교체하는 것이다.
값 객체는 속성을 변경해야 하는 경우 속성을 변경하는 것이 아니다. 새로운 속성을 가진 새로운 값 객체를 만들어 기존 값 객체와 교체하는 식으로 동작한다.
동일한 값 객체들끼리는 비교가 가능해야 한다. 여기서 비교는 크고 작음에 대한 비교가 아닌 등가성 비교를 의미한다.
비교에 대한 부분은 값 객체 내부에 비교에 대한 메서드를 선언해서 사용하는 방식으로 한다.
class Money {
readonly amount: number
readonly currency: string
constructor(amount: number, currency: string) {
this.amount = amount
this.currency = currency
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency
}
}
const money1 = new Money(100, 'USD')
const money2 = new Money(100, 'USD')
const money3 = new Money(200, 'USD')
console.log(money1.equals(money2)) // true
console.log(money1.equals(money3)) // false
값 객체 외부에서 각 속성에 대한 비교로 값 객체를 비교하는 것은 피해야 한다.
값 객체를 사용하지 않고 도메인 객체들을 원시값을 이용해서 표현하고 사용할 수도 있다. 원시값이 아닌 값 객체를 사용하는 방법은 코드양도 늘어나고 복잡한 면이 있다.
원시값은 범용적인 값이며 값 객체를 사용했을 때 확보할 수 있는 장점들이 있다. 값 객체를 사용했을 때 장점은 무엇일까?
값 객체는 값 객체가 도메인 모델에 대한 문서화를 수행한다. 값 객체의 구조, 방어 코드, 액션 등이 해당 도메인 모델에 대한 지식이 없는 사람도 값 객체에 대한 지식을 습득할 수 있는 자체 문서가 된다.
반면 원시값을 사용하게 되면 별도의 문서나 주석을 달아야 하며 잘못 오용될 수 있는 여지도 있다.
도메인 모델 상 값이 가져야하는 형식, 제약이 있다.
원시값의 경우 값을 할당하거나 사용할 때 이러한 제약에 대한 검사를 거쳐야한다. 그렇지 않으면 예상치 못한 문제를 일으킬 수 있다.
값 객체를 사용하는 경우 자체적으로 형식, 제약에 대한 validation을 거칠 수 있다. 이는 개발자가 크게 신경쓰지 않고도 값의 무결성을 지킬 수 있다는 의미가 된다.
값 객체는 해당 객체에 대한 검증, 비교, 액션에 대한 코드들을 가지고 있다. 값 객체에 관련된 코드들은 값 객체에 모아 둘 수 있기 때문에 효율적이다. 변경될 내용이 있더라도 값 객체 내부의 함수만 변경하면 된다.
원시값을 사용한다면 그 때 그 때 구현이 필요하게 된다. 동일한 도메인 모델에 대한 코드가 이 코드를 사용하게 되는 구석구석에 퍼져있게 된다. DRY 원칙을 위반하게 되는 것이다.
특정 도메인 모델이 가능한 액션이 있고 불가능한 액션이 있을 것이다.
예를 들어, 돈을 나타내는 Money 객체가 있다고 해보자.
값 객체에서는 액션들을 구현하거나 구현하지 않음으로서 액션의 수행 가능 여부를 정의할 수 있다.
원시값을 사용하는 경우 액션 가능/불가능의 경계가 없으므로 오용될 여지가 있다.